package nbcp.http


import nbcp.base.exception.ParameterInvalidException
import nbcp.base.extend.*
import nbcp.base.model.HttpFeignLogData
import nbcp.base.model.HttpRequestModel
import nbcp.base.model.HttpResponseModel
import nbcp.base.utils.SpringUtil
import nbcp.base.utils.StringUtil
import nbcp.http.annotation.*
import nbcp.http.event.HttpClientGatewayHostEvent
import nbcp.http.event.HttpClientInitEvent
import nbcp.http.event.HttpClientPostEvent
import nbcp.http.event.HttpClientPreEvent
import org.slf4j.LoggerFactory
import java.lang.invoke.MethodHandles
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Modifier


class HttpClientInvocation(var url: Map<String, String>, var targetType: Class<*>) :
    InvocationHandler {

    private fun getLookupInstance(
        declaringClass: Class<*>
    ): MethodHandles.Lookup {
        var allConstructor = MethodHandles.Lookup::class.java.declaredConstructors;
        //先找两个参数，第一个是Class，第二个是Int的。
        var con2 =
            allConstructor.firstOrNull { it.parameterCount == 2 && it.parameterTypes[0] == Class::class.java && it.parameterTypes[1] == Int::class.java }
        if (con2 != null) {

            if (Modifier.isPrivate(con2.modifiers)) {
                con2.isAccessible = true
            }

            val allModes = MethodHandles.Lookup.PUBLIC or
                    MethodHandles.Lookup.PRIVATE or
                    MethodHandles.Lookup.PROTECTED or
                    MethodHandles.Lookup.PACKAGE

            return con2.newInstance(declaringClass, allModes) as MethodHandles.Lookup
        }

        var con1 = allConstructor.firstOrNull { it.parameterCount == 1 && it.parameterTypes[0] == Class::class.java }
        if (con1 != null) {
            con1.isAccessible = true

            return con1.newInstance(declaringClass) as MethodHandles.Lookup
        }

        throw RuntimeException("找不到 MethodHandles.Lookup 构造函数!");
    }

    override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
        if (method.name == "toString") {
            return proxy.javaClass.interfaces.map { it.simpleName }.joinToString("_")
        }

        // https://www.jianshu.com/p/63691220f81f
        // 看是否是 default 方法
        if (method.isDefault) {
            /**
             * Jdk11, Jdk17，构造参数不同。兼容性处理
             */
            val declaringClass = method.declaringClass

            var method = MethodHandles.lookup()
                .unreflectSpecial(method, declaringClass)
                .bindTo(proxy)

            if (args.isNullOrEmpty()) {
                return method.invokeWithArguments()
            } else {
                return method.invokeWithArguments(*args)
            }
        }
        val type = proxy.javaClass.interfaces[0]
        val httpClient = type.getAnnotation(HttpClient::class.java)
        val request = HttpRequestModel()
        request.name = httpClient.value


        val initEvent = HttpClientInitEvent(request)
        initEvent.request = request
        initEvent.type = type
        SpringUtil.context.publishEvent(initEvent)

        /**
         * 调用
         */
        invokeForResult(request, method, args)


        val preEvent = HttpClientPreEvent(request)
        preEvent.request = request
        preEvent.type = type
        SpringUtil.context.publishEvent(preEvent)


        val gatewayEvent = HttpClientGatewayHostEvent(request)
        gatewayEvent.request = request
        gatewayEvent.type = type


        val requestPath = getRequestHost(gatewayEvent, request.name) + request.url

        request.url = requestPath

        val response = request.doInvoke()
        val postEvent = HttpClientPostEvent(response)
        postEvent.request = (request)
        postEvent.response = (response)
        postEvent.type = (type)
        SpringUtil.context.publishEvent(postEvent)

        val lastFeign = HttpFeignLogData()
        lastFeign.request = (request)
        lastFeign.type = (type)



        if (response == null) {
            return null
        }

        if (response.status === 204) {
            return null
        }

        if (response.status >= 200 && response.status < 300) {
//            var ret = response.getResponseBody();
//            if (ret != null) {
//                return ret;
//            }

            return getSuitableType(response, method)
        }


        var msg = "HttpClient 调用异常, url: " + request.withQueryUrl + " , 状态码: " + response.status
        if (response.responseBody.HasValue) {
            msg += " , 返回: " + response.responseBody
        }


        val non = method.getAnnotation(NonThrow::class.java);
        if (non != null) {
            log.warn(msg);
            return null;
        } else {
            throw HttpClientException(lastFeign, msg)
        }
    }

    private fun getSuitableType(http: HttpResponseModel, method: Method): Any? {
        val type = method.genericReturnType

        if (type === String::class.java) {
            return http.responseBody
        }

        val contentType = http.contentType
        if (contentType != null) {
            val subType = contentType.toLowerCase()
            if (subType.contains("json")) {
                val content = http.responseBody
                return content!!.FromJson(type)
            } else if (subType.contains("text") || subType.contains("plain")) {
                val content = http.responseBody
                return content!!.FromJson(type)
            }
        } else {
            val content = http.responseBody ?: return null
        }
        throw RuntimeException("暂不识别: " + contentType.AsString())
    }

    private fun invokeForResult(
        http: HttpRequestModel,
        method: Method,
        args: Array<Any?>?
    ) {
        val anns = method.annotations
        val get = anns.firstOrNull { it is Get } as Get?
        if (get != null) {
            val path = get.value
            httpRequest(http, RequestMethod.GET, path, method, args)
            return
        }
        val post = anns.firstOrNull { it is Post } as Post?
        if (post != null) {
            val path = post.value
            http.requestHeader.put("content-type", post.contentType)
            httpRequest(http, RequestMethod.POST, path, method, args)
            return
        }
        val put = anns.firstOrNull { it is Put } as Put?
        if (put != null) {
            val url = put.value
            httpRequest(http, RequestMethod.PUT, url, method, args)
            return
        }
        val delete = anns.firstOrNull { it is Delete } as Delete?
        if (delete != null) {
            val path = delete.value
            httpRequest(http, RequestMethod.DELETE, path, method, args)
            return
        }
        val requestMapping = anns.firstOrNull { it is Request } as Request?
        if (requestMapping != null) {
            val path = requestMapping.value
            httpRequest(http, requestMapping.method, path, method, args)
            return
        }
        throw RuntimeException("缺少 nbcp.http.annotation.Request 注解！")
    }

    private fun httpRequest(
        request: HttpRequestModel,
        requestMethod: RequestMethod,
        path: String,
        method: Method,
        args: Array<Any?>?
    ) {
        request.requestMethod = requestMethod.toString()
        request.defPath = path
        var requestPath = path


        if (method.parameterCount > 0) {
            var parameterIndex = -1
            for (parameter in method.parameters) {
                parameterIndex++
                var parameterValue = args?.get(parameterIndex)
                val parameterName = parameter.name
                val anns = parameter.annotations
                val postBody = anns.firstOrNull { it is RequestBody } as RequestBody?
                if (postBody != null) {
                    if (parameter.type.IsSimpleType()) {
                        request.postString = parameterValue.AsStringWithNull()
                    } else {
                        request.postObject = parameterValue
                    }
                    continue
                }
                val pathVariable = anns.firstOrNull { it is PathVariable } as PathVariable?
                if (pathVariable != null) {

                    if (parameterValue == null) {
                        throw ParameterInvalidException("找不到 PathVariable 参数值: ${parameterName}")
                    }

                    requestPath = requestPath.replace(
                        "{" +
                                pathVariable.value.AsString(parameterName)
                                + "}", parameterValue.toString()
                    )
                    continue
                }
                val requestHeader = anns.firstOrNull { it is RequestHeader } as RequestHeader?
                if (requestHeader != null) {
                    if (parameterValue == null) {
                        continue
                    }

                    val headerName = requestHeader.value.AsString(parameterName)
                    if ("Authorization" == headerName) {
                        log.info("接口指定 Authorization: {}", parameterValue)
                    }
                    request.addHeader(headerName, parameterValue.toString())
                    continue
                }
                val requestParam = anns.firstOrNull { it is RequestParam } as RequestParam?
                if (requestParam != null) {
                    if (parameterValue == null) {
                        continue
                    }

                    val key = requestParam.value.AsString(parameterName)
                    request.addQueryObject(key, parameterValue)
                    continue
                }
                request.addQueryObject(parameterName, parameterValue)
            }
        }
        if (requestPath.get(0) != '/') {
            requestPath = "/$requestPath"
        }

        request.url = requestPath;
    }

    private fun getRequestHost(event: HttpClientGatewayHostEvent, clientName: String): String {

        var host = url.getOrDefault(clientName, "")
        if (host.HasValue) {
            log.info("$clientName --> $host")
        } else {
            SpringUtil.context.publishEvent(event)

            if (event.requestHost.HasValue) {
                host = event.requestHost

                log.info("$clientName --> $host")
            } else {
                host = "//$clientName"
            }
        }


        if (host.startsWith("//")) {
            host = "http:$host"
        }

        host = StringUtil.removeEnd(host, '/')
        return host
    }

    companion object {
        private val log = LoggerFactory.getLogger(this::class.java.declaringClass)
    }
}